#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

#define STACK_SIZE 256 // 栈的初始大小定义为256
static int stack[STACK_SIZE] = {0}; // 栈

static int *instructions; // 将用来保存指令的空间
static int instruction_count = 0; // 指令个数
static int instruction_space = 4; // 每条指令默认大小

// 指令集
typedef enum
{
    ADD, // 0 -- [add] 将栈顶两个值相加
    DIV, // 1 -- [div] 将栈顶两个值相除
    HLT, // 2 -- [hlt] 停止
    IF,  // 3 -- [if reg val ip] 条件判断指令
    IFN, // 4 -- [if not reg val ip] 条件判断指令
    GLD, // 5 -- [gld reg] 将寄存器中的值保存到栈中
    GPT, // 6 -- [gpt reg] 将栈顶的值保存到寄存器中
    LOG, // 7 -- log 打印寄存器的值
    MOD, // 8 -- [mod] 将栈顶两个值取模
    MOV, // 9 -- [mov reg_a, reg_b] 将第一个值赋值给第二个值
    MUL, // 10 -- [mul] 将栈顶两个数相乘
    NOP, // 11 -- [nop] 空指令
    POP, // 12 -- [pop] 弹出栈顶第一个值
    PSH, // 13 -- [push val] 将某值个入栈
    SET, // 14 -- [set reg, val] 将一个值赋值给一个寄存器
    SLT, // 15 -- [slt reg_a, reg_b] 判断reg_b是否大于reg_a
    SUB, // 16 -- [sub] 相减

} InstructionSet; // 指令

// 寄存器
typedef enum
{
    A,   // 通用寄存器
    B,   // 通用寄存器
    C,   // 通用寄存器
    D,   // 通用寄存器
    E,   // 通用寄存器
    F,   // 通用寄存器
    I,   // 通用寄存器
    J,   // 通用寄存器
    EX,  // 扩展寄存器
    EAX, // 扩展寄存器
    IP,  // 指令指针寄存器
    SP,  // 栈指针寄存器
    NUM_OF_REGISTERS
} Regitsers;

static int registers[NUM_OF_REGISTERS] = {0}; // 寄存器
static bool is_run = true; // 是否运行
static bool is_jmp = false; // 是否跳转

#define SP (registers[SP]) // SP 宏定义
#define IP (registers[IP]) // IP 宏定义
#define FETCH (instructions[IP])

int Program[] = { // 测试指令
    PSH, 5,
    PSH, 6,
    ADD,
    POP,
    HLT};

void print_stack() {
    for (int i = 0; i < STACK_SIZE; i++)
    {
        printf("0x%08d ", stack[i]);
        if ((i + 1) % 4 == 0)
            printf("\n");
    }
}

void print_registers() {
    for (int i = 0; i < NUM_OF_REGISTERS; i++) {
        printf("0x%08d ", registers[i]);
        if ((i + 1) % 4 == 0)
            printf("\n");
    }
}

void eval(int instr)
{
    is_jmp = false;
    switch (instr)
    {
    case PSH:
    {
        stack[++SP] = instructions[++IP];
        // printf("%d\n", stack[SP]);
        break;
    };
    case POP:
    {
        SP--;
        // printf("%d\n", stack[SP]);
        break;
    };
    case ADD:
    {
        registers[A] = stack[SP--];
        registers[B] = stack[SP--];
        registers[C] = registers[A] + registers[B];
        stack[++SP] = registers[C];
        // printf("%d + %d = %d\n", registers[A], registers[B], registers[C]);
        break;
    };
    case DIV:
    {
        registers[A] = stack[SP--];
        registers[B] = stack[SP--];
        registers[C] = registers[A] / registers[B];
        stack[++SP] = registers[C];
        printf("a / b = %d\n", stack[SP]);
        break;
    };
    case MOD:
    {
        registers[A] = stack[SP--];
        registers[B] = stack[SP--];
        registers[C] = registers[A] % registers[B];
        stack[++SP] = registers[C];
        printf("a % b = %d\n", stack[SP]);
        break;
    };
    case MUL:
    {
        registers[A] = stack[SP--];
        registers[B] = stack[SP--];
        registers[C] = registers[A] * registers[B];
        stack[++SP] = registers[C];
        printf("a * b = %d\n", stack[SP]);
        break;
    };
    case SUB:
    {
        registers[A] = stack[SP--];
        registers[B] = stack[SP--];
        registers[C] = registers[A] - registers[B];
        stack[++SP] = registers[C];
        printf("a - b = %d\n", stack[SP]);
        break;
    };
    case MOV:
    {
        registers[instructions[IP + 1]] = registers[instructions[IP + 2]];
        IP = IP + 2;
        break;
    };
    case SET:
    {
        registers[instructions[IP + 1]] = instructions[IP + 2];
        IP += 2;
        break;
    };
    case HLT:
    {
        is_run = false;
        printf("Execute Finished.\n");
        // print_stack();
        // print_registers();
        break;
    };
    case SLT: {
        IP--;
        registers[instructions[IP]] = registers[instructions[IP + 1]] < registers[instructions[IP]];
        IP += 2;
        break;
    };
    case IF:
    {
        if (registers[instructions[IP + 1]] == instructions[IP + 2])
        {
            
            is_jmp = true;
            IP = instructions[IP + 3]; // 跳过当前指令的后2条指令，转到当前指令后面的的第四条指令
        }
        else
        {
            IP = IP + 3; // 转到当前指令的后面第三条指令
        }
        break;
    };
    case IFN:
    {
        if (registers[instructions[IP + 1]] != instructions[IP + 2])
        {
            is_jmp = true;
            IP = instructions[IP + 3]; // 跳过当前指���的后2条指令，转到当前指令后面的的第四条指令
        }
        else
        {
            IP = IP + 3; // 转到当前指令的后面第三条指令
        }
        break;
    };
    case GLD:
    {
        stack[++SP] = registers[instructions[++IP]];
        break;
    };
    case GPT:
    {
        registers[instructions[++IP]] = stack[SP];
        break;
    };
    case LOG:
    {
        printf("Log: %d = %d\n", instructions[IP + 1], registers[instructions[IP + 1]]);
        IP = IP + 1;
        break;
    };
    case NOP:
    {
        printf("nothing to do.\n");
        break;
    };
    default: {
        printf("Unkonw instruction:%d\n", instr);
        break;
    }
    }
}

int main(int argc, char *argv[])
{
    // 从命令行获取参数
    // 如果参数个数小于2,则报错
    // argc = 2;
    // argv[1] = "sum200.asm";
    if (argc != 2)
    {
        printf("Usage: onevm <filename.asm>\n");
        return -1;
    }
    // 读取汇编文件
    FILE *file = fopen(argv[1], "r");
    if (!file) {
        printf("%s read failed.\n", argv[1]);
        return -1;
    }

    // 申请一块内存空间保存这个文件内容
    instructions = malloc(sizeof(*instructions) * instruction_space);
    int i = 0;
    int num = 0;
    while (fscanf(file, "%d", &num) > 0)
    {
        instructions[i] = num; // 读取文件每一行内容，保存到申请的空间中
        // printf("Line %d: %d\n", i, num);
        i++;
        if (i >= instruction_space) // 同时判断当前写入的是不是大于已申请的空间
        {   // 如果是，则将申请的空间扩大2倍
            instruction_space *= 2;
            instructions = realloc(instructions, sizeof(*instructions) * instruction_space);
        }
    }

    instruction_count = i; // 保存当前读取到指令总条数

    printf("instruction_count: %d\n", instruction_count);

    // 文件读取完毕，关闭文件
    fclose(file);

    IP = 0; // 指令指针初始化为0
    SP--; // 初始化栈指针为-1，表示为空
    while (is_run && IP < instruction_count)
    {
        eval(FETCH);
        if (!is_jmp)
        {
            IP++;
        }
    }

    free(instructions);

    return 0;
}